package com.njxxted.activiti.common.util;

import com.alibaba.fastjson.JSON;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.github.pagehelper.util.StringUtil;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
import com.njxxted.activiti.common.constant.Constant;
import com.njxxted.activiti.common.exception.CustomerException;
import com.njxxted.activiti.modules.process.cmd.RollbackNodeCmd;
import com.njxxted.activiti.modules.process.diagram.CustomProcessDiagramGenerator;
import com.njxxted.activiti.modules.process.diagram.CustomProcessDiagramGeneratorI;
import com.njxxted.activiti.modules.process.extend.dao.ExtendActHistoryDao;
import com.njxxted.activiti.modules.process.extend.entity.ExtendActHistoryEntity;
import de.odysseus.el.ExpressionFactoryImpl;
import de.odysseus.el.util.SimpleContext;
import org.activiti.bpmn.model.Process;
import org.activiti.bpmn.model.*;
import org.activiti.engine.*;
import org.activiti.engine.history.HistoricActivityInstance;
import org.activiti.engine.history.HistoricProcessInstance;
import org.activiti.engine.history.HistoricTaskInstance;
import org.activiti.engine.runtime.Execution;
import org.activiti.engine.runtime.ProcessInstance;
import org.activiti.engine.task.Task;
import org.activiti.spring.SpringProcessEngineConfiguration;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.el.ExpressionFactory;
import javax.el.ValueExpression;
import java.awt.*;
import java.io.InputStream;
import java.util.List;
import java.util.*;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.stream.Collectors;

/**
 * 描述：工作流工具类
 * <p>
 * 作者：Ostrich Hu
 * 时间：2019/9/3 10:32 星期二
 */
public class WorkflowUtils {

    private static Logger logger = LoggerFactory.getLogger(WorkflowUtils.class);

    private static RepositoryService repositoryService = (RepositoryService) ContextFactoryUtil.getBean("repositoryService");
    private static HistoryService historyService = (HistoryService) ContextFactoryUtil.getBean("historyService");
    private static RuntimeService runtimeService = (RuntimeService) ContextFactoryUtil.getBean("runtimeService");
    private static TaskService taskService = (TaskService) ContextFactoryUtil.getBean("taskService");
    private static ManagementService managementService = (ManagementService) ContextFactoryUtil.getBean("managementService");
    private static ObjectMapper objectMapper = (ObjectMapper) ContextFactoryUtil.getBean("objectMapper");
    private static ExtendActHistoryDao extendActHistoryDao = (ExtendActHistoryDao) ContextFactoryUtil.getBean("ExtendActHistoryDao");
    private static SpringProcessEngineConfiguration peConfiguration = (SpringProcessEngineConfiguration) ContextFactoryUtil.getBean("springProcessEngineConfiguration");


    /**
     * 根据 BpmnModel 获取流程图像
     *
     * @param bpmnModel bpmn模型
     * @return InputStream
     */
    public static InputStream getFlowImage(BpmnModel bpmnModel) {
        //ProcessDiagramGenerator processDiagramGenerator = peConfiguration.getProcessDiagramGenerator();
        CustomProcessDiagramGenerator customProcessDiagramGenerator =
                (CustomProcessDiagramGenerator) peConfiguration.getProcessDiagramGenerator();
        return customProcessDiagramGenerator.generateDiagram(bpmnModel, "png",
                Collections.emptyList(), Collections.emptyList(),
                "宋体", "宋体", "宋体",
                null, 1.0);
    }

    /**
     * 根据流程实例Id 查询 流程路径图
     *
     * @param instanceId 流程实例id
     * @return
     */
    public static InputStream getFlowExecutionRouteImage(String instanceId) {
        HistoricProcessInstance processInstance = historyService.createHistoricProcessInstanceQuery()
                .processInstanceId(instanceId).singleResult();
        BpmnModel bpmnModel = repositoryService.getBpmnModel(processInstance.getProcessDefinitionId());

        List<HistoricActivityInstance> highLightedActivitiList = historyService.createHistoricActivityInstanceQuery()
                .processInstanceId(instanceId).orderByHistoricActivityInstanceStartTime().asc().list();

        //查询出历史
        ExtendActHistoryEntity extendActHistoryEntity = new ExtendActHistoryEntity();
        extendActHistoryEntity.setProcessInstanceId(instanceId);
//        extendActHistoryEntity.setTaskDefKey(flowNode.getId());
//        List<HistoryTaskInstanceEntity> historyTaskInstanceEntities = extendActHistoryDao.queryObjectByPidTid(instanceId, null);
//        List<ExtendActHistoryEntity> extendActHistories = extendActHistoryDao.queryListByBean(extendActHistoryEntity);
        //去除退回的节点
//        extendActHistories.forEach(ext -> {
//            highLightedActivities.removeIf(act -> Constant.Flow.TASK_BACK.equals(ext.getOperateType()) ||
//                    Constant.Flow.TASK_BACK_2_START.equals(ext.getOperateType()));
//        });

        // 高亮环节id集合
        List<String> highLightedActivitis = new CopyOnWriteArrayList<>();
        // 高亮线路id集合
        List<String> highLightedFlows = getHighLightedFlows(highLightedActivitiList, processInstance.getProcessDefinitionId());
        //获取高亮环节id
        highLightedActivitiList.parallelStream().forEach(historicActivity -> {
            String activityId = historicActivity.getActivityId();
            String hisTaskId = historicActivity.getId();
//            HistoricActivityInstanceEntityImpl entity = (HistoricActivityInstanceEntityImpl) historicActivity;
//            logger.debug("节点名称[{}]任务id[{}]", entity.getActivityName(), hisTaskId);
            highLightedActivitis.add(activityId);
        });

        //获取当前执行的节点id
        Set<String> currIds = runtimeService.createExecutionQuery().processInstanceId(processInstance.getId()).list()
                .stream().map(Execution::getActivityId).collect(Collectors.toSet());

        CustomProcessDiagramGeneratorI diagramGenerator = (CustomProcessDiagramGeneratorI) peConfiguration.getProcessDiagramGenerator();

        Color[] colors = {Constant.Flow.COLOR_NORMAL, Constant.Flow.COLOR_CURRENT,
                Constant.Flow.COLOR_REBACK, Constant.Flow.COLOR_SUSPPEND,
                Constant.Flow.COLOR_CANCEL, Constant.Flow.COLOR_COMMON};

        return diagramGenerator.generateDiagram(bpmnModel, "png", highLightedActivitis, highLightedFlows,
                "黑体", "黑体", "黑体", null, 1.0, colors, currIds, instanceId);
    }

    /**
     * 根据 BpmnModel 获取流程中的所有节点(不包含顺序流)
     *
     * @param bpmnModel bpmn模型
     * @return List<Map < String, Object>>
     */
    public static List<Map<String, Object>> getAllNodeInProcess(BpmnModel bpmnModel) {
        List<Map<String, Object>> nodeInfoList = new CopyOnWriteArrayList<>();
        Process mainProcess = bpmnModel.getMainProcess();
        List<FlowNode> flowNodeList = mainProcess.findFlowElementsOfType(FlowNode.class);
        flowNodeList.parallelStream().forEach(flowNode -> {
            Map<String, Object> nodeInfo = new HashMap<>();
            List<SequenceFlow> outgoingFlows = flowNode.getOutgoingFlows();
            GraphicInfo graphicInfo = bpmnModel.getGraphicInfo(flowNode.getId());
            List<Map> targetMapList = new CopyOnWriteArrayList<>();
            outgoingFlows.forEach(sequenceFlow -> {
                Map<String, Object> targetMap = new HashMap<>();
                FlowElement targetFlowElement = sequenceFlow.getTargetFlowElement();
                targetMap.put("targetName", targetFlowElement.getName());
                targetMap.put("sequenceId", sequenceFlow.getId());
                targetMap.put("targetId", targetFlowElement.getId());
                targetMapList.add(targetMap);
            });
            //设置节点的变量信息
            nodeInfo.put("outgoingTransitions", outgoingFlows);
            nodeInfo.put("targetMapList", targetMapList);
            nodeInfo.put("nodeKey", flowNode.getId());
            nodeInfo.put("nodeType", convertType(flowNode));
            nodeInfo.put("nodeName", flowNode.getName());
            nodeInfo.put("x", graphicInfo.getX());
            nodeInfo.put("y", graphicInfo.getY());
            nodeInfo.put("width", graphicInfo.getWidth());
            nodeInfo.put("height", graphicInfo.getHeight());
            nodeInfoList.add(nodeInfo);
        });
        return nodeInfoList;
    }

    /**
     * 根据节点Id取得当前节点的下一流向流程节点,如果Id为空则默认为首节点（条件路由则只返回符合条件的流向）
     *
     * @param defId       流程定义Id
     * @param currNodeKey 当前流程节点
     * @param elMap       流程变量el表达式集合
     * @return
     */
    public static List<FlowNode> getNextNode(String defId, String currNodeKey, Map<String, Object> elMap) {
        BpmnModel bpmnModel = repositoryService.getBpmnModel(defId);
        Process mainProcess = bpmnModel.getMainProcess();
        List<FlowNode> flowNodes = new CopyOnWriteArrayList<>();
        //1.获取流程所有节点
        List<FlowNode> flowNodeList = mainProcess.findFlowElementsOfType(FlowNode.class);
        if (null == currNodeKey) {
            List<SequenceFlow> outgoingFlows = flowNodeList.get(0).getOutgoingFlows();
            //startEvent节点
            if (Constant.Flow.START_EVENT.equals(convertType(flowNodeList.get(0)))) {
                List<FlowNode> flowDirection = getFlowDirection(outgoingFlows, elMap);
                flowNodes.addAll(flowDirection);
            }
        } else {
            //2.循环 flowNodeList 并判断出当前流程所处节点，然后得到当前节点实例，
            flowNodeList.parallelStream().forEach(flowNode -> {
                // 根据节点实例获取所有从当前节点出发的路径，然后根据路径获得下一个节点实例：
                if (currNodeKey.equals(flowNode.getId())) {
                    //获取流向下一节点的顺序流
                    List<SequenceFlow> outgoingFlows = flowNode.getOutgoingFlows();
                    //判断分支流向
                    List<FlowNode> flowDirection = getFlowDirection(outgoingFlows, elMap);
                    flowNodes.addAll(flowDirection);
                }
            });
        }
        return flowNodes;
    }

    /**
     * 获取当前执行节点的上一个已执行任务节点
     *
     * @param instanceId 流程实例id
     * @return FlowElement
     */
    public static List<HistoricTaskInstance> getHisPreActivity(String instanceId) {
        return historyService.createHistoricTaskInstanceQuery()
                .processInstanceId(instanceId)
                .orderByHistoricTaskInstanceStartTime()
                .asc()
                .list();
    }

    /**
     * 获取当前执行节点的上一个已执行节点
     *
     * @param instanceId        流程实例id
     * @param taskDefinitionKey 当前执行任务定义key
     * @param isStarted         是否开始节点
     * @return FlowElement
     */
    public static FlowNode getHasBeenDonePreNode(String instanceId, String taskDefinitionKey, boolean isStarted) {
        //查询流程实例
        ProcessInstance instance = runtimeService.createProcessInstanceQuery().processInstanceId(instanceId).singleResult();
        //根据流程实例获取流程定义模型
        BpmnModel bpmnModel = repositoryService.getBpmnModel(instance.getProcessDefinitionId());
        //根据流程定义模型获取所有节点的（有序）集合
        Set<String> taskDefKeys = bpmnModel.getLocationMap().keySet();
        List<String> taskDefKeyList = Lists.newArrayList(taskDefKeys);
        //获取当前节点处于所有用户任务节点的索引位置
        int index = taskDefKeyList.indexOf(taskDefinitionKey);
        if (isStarted) {
            //查找出第一个用户任务节点
            return (FlowNode) bpmnModel.getFlowElement(taskDefKeyList.get(1));
        } else {
            //判断流程中是否有gate节点。如果有则通过流程历史处理，否则直接从定义模型中处理。
            if (taskDefKeyList.indexOf("gate") == -1) {
                //查找出当前节点的上一个节点
                return (FlowNode) bpmnModel.getFlowElement(taskDefKeyList.get(index - 1));
            } else {
                //查询流程历史s
                List<HistoricTaskInstance> hisPreActivityList = getHisPreActivity(instanceId);
                if (hisPreActivityList.size() > 1) {
                    //排除重复的历史节点
                    List<String> finalTaskDefKeyList = Lists.newArrayList();
                    hisPreActivityList.parallelStream().forEachOrdered(historicTaskInstance -> {
                        boolean present = finalTaskDefKeyList.parallelStream()
                                .filter(o -> o.equals(historicTaskInstance.getTaskDefinitionKey()))
                                .findFirst().isPresent();
                        if (!present) {
                            finalTaskDefKeyList.add(historicTaskInstance.getTaskDefinitionKey());
                        }
                    });
                    //找出当前节点所处的下标
                    index = finalTaskDefKeyList.indexOf(taskDefinitionKey);
                    return (FlowNode) bpmnModel.getFlowElement(finalTaskDefKeyList.get(index - 1));
                } else {
                    throw new ActivitiException("只有一个节点，不能进行驳回操作");
                }
            }
        }
    }

    /***
     * 回退
     * @param currTaskId 当前节点id
     * @param deleteReason 回退原因
     * @param isStarted 是否退回到开始节点
     */
    public static void rollback(String currTaskId, String deleteReason, boolean isStarted) {
        if (StringUtil.isEmpty(currTaskId)) {
            throw new CustomerException("任务iD为空！");
        }
        Task currExecutionTask = getTaskById(currTaskId);
        String processInstanceId = currExecutionTask.getProcessInstanceId();
        String taskDefinitionKey = currExecutionTask.getTaskDefinitionKey();
        //执行自定义的回退命令
        RollbackNodeCmd rollbackNodeCmd = new RollbackNodeCmd(currTaskId);
        rollbackNodeCmd.deleteReason = deleteReason;
        rollbackNodeCmd.isStarted = isStarted;
        rollbackNodeCmd.flowNode = getHasBeenDonePreNode(processInstanceId, taskDefinitionKey, isStarted);
        managementService.executeCommand(rollbackNodeCmd);
    }

    /**
     * 根据任务ID获取对应的流程实例
     *
     * @param taskId 任务ID
     * @return
     */
    public static ProcessInstance getProcessInstanceByTaskId(String taskId) {
        ProcessInstance processInstance = runtimeService.createProcessInstanceQuery()
                .processInstanceId(getTaskById(taskId).getProcessInstanceId()).singleResult();
        if (processInstance == null) {
            throw new CustomerException("流程实例未找到!");
        }
        return processInstance;
    }

    /**
     * 各种情况的下级节点
     *
     * @param outgoingFlows 流向列表
     * @param elMap         变量
     * @return List<FlowElement>
     */
    private static List<FlowNode> getFlowDirection(List<SequenceFlow> outgoingFlows, Map<String, Object> elMap) {
        List<FlowNode> flowElements = new CopyOnWriteArrayList<>();
        outgoingFlows.parallelStream().forEach(outgoingFlow -> {
            //1.获取顺序流流向流程的目标元素
            FlowNode targetFlowElement = (FlowNode) outgoingFlow.getTargetFlowElement();
//            String targetFlowElementName = targetFlowElement.getName();
            String targetFlowNodeType = convertType(targetFlowElement);
            //2.判断目标元素类型
            switch (targetFlowNodeType) {
                case Constant.Flow.USER_TASK:
                case Constant.Flow.END_EVENT:
                    flowElements.add(targetFlowElement);
                    break;
                case Constant.Flow.EXCLUSIVE_GATEWAY:
                    //根据流程变量判断网关条件的流向
                    List<SequenceFlow> exlcusiveOutgoingFlows = targetFlowElement.getOutgoingFlows();
                    exlcusiveOutgoingFlows.parallelStream().forEach(exlcusiveOutgoingFlow -> {
                        //获取分支节点流向中的判断el表达式
                        String conditionExpression = exlcusiveOutgoingFlow.getConditionExpression();
                        for (String key : elMap.keySet()) {
                            if (conditionExpression.contains(key)) {
                                boolean condition = isCondition(key, conditionExpression, String.valueOf(elMap.get(key)));
                                //判断该流向是否符合传入的参数条件且不是网关节点
                                if (exlcusiveOutgoingFlow.getTargetFlowElement() instanceof ExclusiveGateway) {
                                    //迭代获取
                                    getFlowDirection(exlcusiveOutgoingFlows, elMap);
                                } else if (condition) {
                                    flowElements.add((FlowNode) exlcusiveOutgoingFlow.getTargetFlowElement());
                                }
                            }
                        }
                    });
                    break;
                case Constant.Flow.PARALLEL_GATEWAY:
                    //并行网关
                    List<SequenceFlow> parallelNodeOutgoingFlows = ((FlowNode) targetFlowElement).getOutgoingFlows();
                    parallelNodeOutgoingFlows.parallelStream().forEach(parallelOutgoingFlow -> {
                        flowElements.add((FlowNode) parallelOutgoingFlow.getTargetFlowElement());
                    });
                    break;
            }
        });

        return flowElements;
    }

    /**
     * 根据任务Id,查找当前任务
     *
     * @param taskId 任务Id
     * @return
     */
    public static Task getTaskById(String taskId) {
        return taskService.createTaskQuery().taskId(taskId).singleResult();
    }

    /**
     * 将流程节点转为相对应节点名称字符串
     *
     * @param flowNode 流程节点
     * @return string
     */
    private static String convertType(FlowNode flowNode) {
        String type = "";
        if (flowNode instanceof StartEvent) {
            type = Constant.Flow.START_EVENT;
        } else if (flowNode instanceof EndEvent) {
            type = Constant.Flow.END_EVENT;
        } else if (flowNode instanceof ExclusiveGateway) {
            type = Constant.Flow.EXCLUSIVE_GATEWAY;
        } else if (flowNode instanceof ParallelGateway) {
            type = Constant.Flow.PARALLEL_GATEWAY;
        } else if (flowNode instanceof UserTask) {
            type = Constant.Flow.USER_TASK;
        }
        return type;
    }

    /**
     * 根据key和value判断el表达式是否通过信息
     *
     * @param key          el表达式key信息
     * @param elExpression el表达式信息
     * @param elValue      el表达式传入值信息
     * @return boolean
     */
    private static boolean isCondition(String key, String elExpression, String elValue) {
        logger.debug(" el表达式key信息：{}\nel表达式信息：{}\nel表达式传入值信息:{}", key, elExpression, elValue);
        ExpressionFactory factory = new ExpressionFactoryImpl();
        SimpleContext context = new SimpleContext();
        context.setVariable(key, factory.createValueExpression(elValue, String.class));
        ValueExpression e = factory.createValueExpression(context, elExpression, boolean.class);
        return (Boolean) e.getValue(context);
    }

    /**
     * 获取需要高亮的线
     *
     * @param processDefId 流程定义id
     * @param instances    流程节点历史
     * @return
     */
    private static List<String> getHighLightedFlows(List<HistoricActivityInstance> instances, String processDefId) {
        BpmnModel bpmnModel = repositoryService.getBpmnModel(processDefId);
        // 用以保存高亮的线flowId
        List<String> highFlows = new ArrayList<>();
        //1.对历史流程节点进行遍历
        for (int i = 0; i < instances.size() - 1; i++) {
            //2. 得到节点定义的详细信息
            FlowNode flowNode = (FlowNode) bpmnModel.getFlowElement(instances.get(i).getActivityId());
            //2.1 用以保存开始时间相同的节点
            List<FlowNode> sameStartTimeNodes = new ArrayList<>();
            FlowNode sameFlowNode1 = (FlowNode) bpmnModel.getFlowElement(instances.get(i + 1).getActivityId());
            //3. 将后面第一个节点放在时间相同节点的集合里
            sameStartTimeNodes.add(sameFlowNode1);
            for (int j = 0; j < instances.size() - 1; j++) {
                HistoricActivityInstance node1 = instances.get(j);// 后续第一个节点
                HistoricActivityInstance node2 = instances.get(j + 1);// 后续第二个节点
                if (Math.abs(node1.getStartTime().getTime() - node2.getStartTime().getTime()) < 200) {
                    //3.1 如果第一个节点和第二个节点开始时间相同保存
                    FlowNode sameFlowNode2 = (FlowNode) bpmnModel.getFlowElement(node2.getId());
                    sameStartTimeNodes.add(sameFlowNode2);
                } else {
                    //3.2 有不相同跳出循环
                    break;
                }
            }
            //4. 取出节点的所有出去的线
            List<SequenceFlow> outgoingFlows = flowNode.getOutgoingFlows();
            outgoingFlows.forEach(outgoingFlow -> {
                //4.1 对所有的线进行遍历
                FlowNode targetFlowNode = (FlowNode) outgoingFlow.getTargetFlowElement();
                //4.2 如果取出的线的目标节点存在时间相同的节点里，保存该线的id，进行高亮显示
                if (sameStartTimeNodes.contains(targetFlowNode)) {
                    highFlows.add(outgoingFlow.getId());
                }
            });
        }
        return highFlows;
    }
}
